﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;
using System.Data.Services.Client;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
using AntsCode.StorageClientExtensions;

namespace AntsCode.DevStorageSyncLibrary
{
    /// <summary>
    /// 2009 Anthony Super
    /// http://antscode.blogspot.com
    /// </summary>
    public class Synchroniser
    {
        public Synchroniser(string[] assemblyFiles)
        {
            // Get the entity sets contained in the assemblies
            this.EntitySets = this.GetEntitySetsFromAssemblies(assemblyFiles);

            // Initialise the cloud storage account
            this.Account = CloudStorageAccount.FromConfigurationSetting("StorageConnectionString");
        }

        public void SyncAllEntitySets()
        {
            foreach (DataServiceEntitySet entitySet in this.EntitySets)
            {
                this.UpdateTableProperties(entitySet);
            }
        }

        public void SyncEntitySet(string entitySetName)
        {
            DataServiceEntitySet entitySet = this.EntitySets.First(x => x.Name == entitySetName);
            this.UpdateTableProperties(entitySet);
        }

        /// <summary>
        /// Returns all entity sets within the specified assembly.
        /// Entity sets must be properties of type DataServiceQuery, contained in a class that inherits
        /// from DataServiceContext.
        /// </summary>
        /// <param name="assemblyFile">The path & filename of the assembly.</param>
        /// <returns>An IEnumerable of DataServiceEntitySet objects.</returns>
        private IEnumerable<DataServiceEntitySet> GetEntitySetsFromAssemblies(string[] assemblyFiles)
        {
            List<DataServiceEntitySet> entities = new List<DataServiceEntitySet>();

            foreach (string assemblyFile in assemblyFiles)
            {
                Assembly assembly = Assembly.LoadFrom(assemblyFile);

                // Get the types from the assebmly
                Type[] types;

                try
                {
                    types = assembly.GetTypes();
                }
                catch (ReflectionTypeLoadException e)
                {
                    // If any types could not be loaded, the successful types are returned in the exception
                    types = e.Types;
                }

                // Look for classes that inherit from DataServiceContext, these will contain our entity sets
                for (int i = 0; i < types.Length; i++)
                {
                    if (types[i] != null && (types[i].IsSubclassOf(typeof(DataServiceContext)) || types[i].IsSubclassOf(typeof(XTableServiceContext))))
                    {
                        PropertyInfo[] properties = types[i].GetProperties();

                        for (int j = 0; j < properties.Length; j++)
                        {
                            Type type = properties[j].PropertyType;

                            // Properties of type DataServiceQuery are entity sets
                            if (type.BaseType == typeof(DataServiceQuery) || type.GetInterface("IXTableServiceQuery", false) != null)
                            {
                                Type entityType = type.GetGenericArguments()[0];

                                // Check if this entity set has already been added
                                if (!entities.Exists(x => x.Name == properties[j].Name))
                                {
                                    entities.Add(new DataServiceEntitySet()
                                    {
                                        Name = properties[j].Name,
                                        EntityType = entityType,
                                        Assembly = assembly
                                    });
                                }
                            }
                        }
                    }
                }
            }

            return entities.OrderBy(x => x.Name);
        }

        private void UpdateTableProperties(DataServiceEntitySet entitySet)
        {
            // First ensure the table exists
            CloudTableClient client = new CloudTableClient(this.Account.TableEndpoint.ToString(), this.Account.Credentials);

            if (!client.DoesTableExist(entitySet.Name))
            {
                // Table does not exist, so exit
                return;
            }

            // Now insert & delete a record
            TableServiceContext context = new TableServiceContext(this.Account.TableEndpoint.ToString(), this.Account.Credentials);

            // For some reason, for the development storage to update the schema, we first need to run a query
            // on the current entity.
            // WHY?!?!?!?!?!
            var query = from x in context.CreateQuery<TableServiceBaseEntity>(entitySet.Name)
                        select x;

            var temp = query.ToList();

            // Create a new entity of this type
            object entity = entitySet.Assembly.CreateInstance(entitySet.EntityType.FullName);

            // Set all properties
            PropertyInfo[] entityProperties = entity.GetType().GetProperties();

            for (int j = 0; j < entityProperties.Length; j++)
            {
                SetPropertyValue(entity, entityProperties[j]);
            }

            // Add the object to update the schema
            context.AddObject(entitySet.Name, entity);
            context.SaveChanges();

            // Now delete it
            context.DeleteObject(entity);
            context.SaveChanges();
        }

        private void SetPropertyValue(object entity, PropertyInfo property)
        {
            string fullType = property.PropertyType.FullName;

            // This strange method is used due to nullable types - the only way I can seem to 
            // determine the underlying type is to read the FullName attribute.
            if (fullType.IndexOf("System.String") > -1)
            {
                property.SetValue(entity, Guid.NewGuid().ToString(), null);
            }
            else if (fullType.IndexOf("System.Int32") > -1)
            {
                property.SetValue(entity, 0, null);
            }
            else if (fullType.IndexOf("System.Int64") > -1)
            {
                property.SetValue(entity, 0, null);
            }
            else if (fullType.IndexOf("System.Double") > -1)
            {
                property.SetValue(entity, 0.0, null);
            }
            else if (fullType.IndexOf("System.DateTime") > -1)
            {
                property.SetValue(entity, DateTime.UtcNow, null);
            }
            else if (fullType.IndexOf("System.Boolean") > -1)
            {
                property.SetValue(entity, false, null);
            }
            else if (fullType.IndexOf("System.Guid") > -1)
            {
                property.SetValue(entity, Guid.NewGuid(), null);
            }
        }

        /// <summary>
        /// The entity sets found in the specified assembly.
        /// </summary>
        public IEnumerable<DataServiceEntitySet> EntitySets
        {
            get;
            private set;
        }

        /// <summary>
        /// The active cloud storage account.
        /// </summary>
        public CloudStorageAccount Account
        {
            get;
            private set;
        }
    } 
}